#ifndef __OBLIVIOUS__
#define __OBLIVIOUS__

#include <assert.h>
#include <vector>
#include <cstring>
#include "mkl.h"

using namespace std;

class Matrix;
class MatrixSlice;

// Manages the matrix's memory, keeps track of # of rows and cols.
class Matrix {
  friend class MatrixSlice;
  
  int rows;
  int columns;

protected:
  double *d; // Accessed by MatrixSlice.
  
public:

  Matrix(int rows, int columns);
  Matrix(double* data, int rows, int columns);
  Matrix();
  //~Matrix();
  
  void resize(int new_rows, int new_columns);
  void zero();
  
  inline double &at(int row, int column) {
    // assert(row >= 0 && row < rows);
    // assert(column >= 0 && column < columns);
    return *(d + row * columns + column);
  }
  
  inline const double &at(int row, int column) const {
    return *(d + row * column + column);
  }

  inline const double* data() const {
    return d;
  }
  
  // Convenience function, alias for MatrixSlice(this).
  MatrixSlice slice() const;
  
  inline int get_rows() const { return rows; }
  
  inline int get_columns() const { return columns; }
  
  // Dumps to stdout in CSV form.
  void dump() const;
  
};

// A view into a matrix. Has functions for splitting along rows or columns.
class MatrixSlice {
  
  const Matrix *parent;
  double *d;
  int rows;
  int columns;
  int row_stride;
  
  // necessary for after we split columns
  // int col_stride;
  
  MatrixSlice();
  
public:
  MatrixSlice(const Matrix *parent);
  
  // Accessing the row-major matrix would be faster if we just returned
  // a pointer, but a greater level of abstraction will be helpful later on 
  // because we will not have that option with the blocked or bit-interleaved 
  // storage formats.
  inline double &at(int row, int column) {
    // assert(row >= 0 && row < rows);
    // assert(column >= 0 && column < columns);
    return *(d + row * row_stride + column);
  }
  
  MatrixSlice(const MatrixSlice& copy) {

    parent = copy.parent;
    d = copy.d;
    rows = copy.rows;
    columns = copy.columns;
    row_stride = copy.row_stride;
    
  }

  void dump() const;
  
  inline const double &at(int row, int column) const {
    return *(d + row * row_stride + column);
  }
  
  inline int get_rows() const { return rows; }
  
  inline int get_columns() const { return columns; }

  inline double* get_ptr() const {return d;}

  inline int get_row_stride() const {return row_stride;}
  
  // Returns 2 sub-slices, (A0; A1).
  vector<MatrixSlice> split_rows() const;
  
  // Returns 2 sub-slices, (A0 A1).
  vector<MatrixSlice> split_columns() const;
  
};

void mat_mult_rec(const MatrixSlice a, const MatrixSlice b, MatrixSlice c, int base_case_size);

// Multiplies a * b, stores the result in c.
void mat_mult_rec(const Matrix &a, const Matrix &b, Matrix &c, int base_case_size);

int get_row_count(int total_rows, int my_rank, int num_procs);

#endif
